<?php
/**
 * user certificate model class
 *
 * @author raphael seebacher <raphasee@ee.ethz.ch>
 * @version 0.01
 */
class user_certificate extends db_model {
    protected $data = Array(
      'id' => NULL,
      'user_id' => NULL,
      'serial_number' => NULL,
      'issued' => NULL,
      'valid_from' => NULL,
      'valid_to' => NULL,
      'revoked' => NULL,
      'certificate' => NULL,
    );

    public function save() {
        if ($this->data['user_id'] == NULL
          || $this->data['serial_number'] == NULL
          || $this->data['valid_from'] == NULL
          || $this->data['valid_to'] == NULL
          || $this->data['certificate'] == NULL
        ) {
            throw new model_exception(
              'Cannot save. Not all mandatory data provided.'
            );
        }

        $this->escape_data();

        if ($this->data['id'] == NULL) {
            $q = 'INSERT INTO user_certificate '
                .'(user_id, serial_number, '
                .'valid_from, valid_to, revoked, certificate) '
                .'VALUES ('
                .($this->data['user_id']).','
                .($this->data['serial_number']).','
                .'\''.($this->data['valid_from']).'\','
                .'\''.($this->data['valid_to']).'\','
                .($this->data['revoked'] == NULL ? 'NULL'
                       : '\''.$this->data['revoked'].'\'').','
                .'\''.($this->data['certificate']).'\')';
        }
        else {
            $q = 'UPDATE user_certificate '
                .'SET '
                .'user_id='.($this->data['user_id']).', '
                .'serial_number='.($this->data['serial_number']).', '
                .'valid_from=\''.($this->data['valid_from']).'\', '
                .'valid_to=\''.($this->data['valid_to']).'\', '
                .'revoked='.($this->data['revoked'] == NULL ? 'NULL'
                       : '\''.$this->data['revoked'].'\'').', '
                .'certificate=\''.($this->data['certificate']).'\' '
                .'WHERE id='.($this->data['id']);
        }

        return self::$dbc->query_assoc($q);
    }

    public static function construct_from_x509_certificate(
      $x509_certificate, $user_id
    ) {
        $x509_resource = openssl_x509_read($x509_certificate);
        $certificate_data = openssl_x509_parse($x509_resource);

        $uc = new self();
        $uc->set('certificate', $x509_certificate);
        $uc->set('user_id', $user_id);

        $uc->set('serial_number', $certificate_data['serialNumber']);
        $uc->set('valid_from', date('Y-m-d H:i:s', $certificate_data['validFrom_time_t']));
        $uc->set('valid_to', date('Y-m-d H:i:s', $certificate_data['validTo_time_t']));

        return $uc;
    }

    public static function get_certificate_revocation_list(
      $last_update_timestamp=NULL
    ) {
        if ($last_update_timestamp == NULL) {
            $q = 'SELECT * '
                .'FROM user_certificate '
                .'WHERE NOW() BETWEEN valid_from AND valid_to '
                .'AND revoked IS NOT NULL';
        }
        else {
            $q = 'SELECT * '
                .'FROM user_certificate '
                .'WHERE NOW() BETWEEN valid_from AND valid_to '
                .'AND revoked IS NOT NULL '
                .'AND revoked>\''
                .date('Y-m-d H:i:s', $last_update_timestamp).'\'';
        }
        $dbc = db_controller::get_instance();
        $revoked_certificates = $dbc->query_assoc($q);

        $result = Array();
        foreach ($revoked_certificates as $certificate) {
            $result[] = new user_certificate($certificate);
        }

        return $result;
    }

    public static function get_current_certificate($user_id) {
        $dbc = db_controller::get_instance();
        $user_id = $dbc->escape(intval($user_id));
        $q = 'SELECT * '
            .'FROM user_certificate '
            .'WHERE user_id='.$user_id.' '
            .'AND revoked IS NULL '
            .'AND NOW() BETWEEN valid_from AND valid_to '
            .'ORDER BY issued DESC '
            .'LIMIT 1';

        $certificate = $dbc->query_assoc($q);

        if (!is_array($certificate) || !count($certificate)) {
            throw new model_exception('No certificate found for given user.');
        }

        return new self($certificate[0]);
    }

    public function get_serial_number_dec() {
        return $this->get('serial_number');
    }

    public function get_serial_number_hex() {
        if ($this->get('serial_number') < 16) {
            return '0'.strtoupper(dechex($this->get('serial_number')));
        }
        else {
            return strtoupper(dechex($this->get('serial_number')));
        }
    }

    public function get_timestamp($field) {
        $time_fields = Array(
          'issued',
          'valid_from',
          'valid_to',
          'revoked',
        );

        if (!in_array($field, $time_fields)) {
            throw new model_exception('Not a datetime field!');
        }

        $datetime = DateTime::createFromFormat(
          'Y-m-d H:i:s',
          $this->get($field)
        );

        return $datetime->getTimestamp();
    }

    public function is_revoked() {
        return ($this->data['revoked'] != NULL);
    }
    
    public function revoke() {
        if ($this->is_revoked()) {
            throw new model_exception('Certificate already revoked.');
        }
        else {
            $cmd = '../scripts/certificate_authority/revoke_certificate.sh '
                .$this->get_serial_number_hex();
            exec($cmd, $output, $return_code);

            if ($return_code != 0) {
                throw new error('Certificate could not be revoked!');
            }

            $openssl_ca_database = file_get_contents(
              '../data/certificate_authority/database.txt'
            );

            $pattern = '/R\t'
                .'[0-9]{12}Z\t'
                .'(?P<revocation_time>[0-9]{12})Z\t'
                .$this->get_serial_number_hex().'/';

            if (!preg_match($pattern, $openssl_ca_database, $matches)) {
                throw new error(
                  'Certificate revocation: Database synchronization failed!'
                );
            }

            $revocation_datetime = DateTime::createFromFormat(
              'ymdHis',
              $matches['revocation_time'],
              new DateTimeZone('Europe/London')
            );

            $this->set('revoked', date('Y-m-d H:i:s', $revocation_datetime->getTimestamp()));
            $this->save();
        }
    }
}
?>